#include "dx8_gen_atlas.h"
#include "dx8_graphics.h"
#include "dx8_surface.h"

#include "input/mouse.h"
#include "graphics/graphics_util.h"
#include "application/game_application.h"
#include "font/font_manager.h"
#include "dd/dd_man.h"

#include "script/file_list.h"
#include "script/write_lua.h"
#include "script/scripthelper.h"

#include "util/timer.h"				// log
#include "util/log.h"				// log
#include "util/common_macros.h"		// macros
#include "util/mmgr.h"		    

#include <cassert>
#include <algorithm>
using namespace script;


DX8GenAtlas::DX8GenAtlas() : 
	m_AtlasW(1024), m_AtlasH(1024), m_AtlasD(32)
{

}

DX8GenAtlas::~DX8GenAtlas()
{

}

void		DX8GenAtlas::Render()
{
	math::Vec2	Size;
	int			Width = 0;
	int			Pages = (int)m_Pages.size();

	if (m_Pages.empty()) return;

	for (int i=1; i<10; i++)
	{
		if (Pages <= (i*i))
		{
			Width = i;
			break;
		}
	}
	
	Size = math::Vec2(1.0f/(float)Width, 1.0f/(float)Width);
	math::Vec2	MousePos = GET_MOUSE().GetPos();


	Graphics::Vertex2D*	VertA = Graphics::GetTempVert();
	for (int i =0; i<Pages; i++)
	{
		AtlasPage*	pPage = m_Pages[i];
		int		x = i/Width;
		int		y = i%Width;

		math::Vec2	P(x*Size.x, y*Size.y);
		int	Index = GraphicsUtil::FillQuad(P, Size, math::Vec2(0,0), math::Vec2(1,1), 0xFFFFFFFF, VertA);
		GetDXGraphics()->GetDevice()->SetTexture(0, pPage->pTexture);
		GetGraphics()->DrawVertex2D(VertA, Index);

		// mouse over information
		if (AtlasNode* pNode = pPage->Root.GetNodeByPos(MousePos, P, Size))
		{
			if (pNode->pEntry)
			{
				FontManager::Instance().Print(math::Vec2(0.01f, 0.3f), "Current : %s (%i x %i)", pNode->pEntry->Name.c_str(), pNode->pEntry->Rect.w, pNode->pEntry->Rect.h);
			}
		}
	}
	FontManager::Instance().Print(math::Vec2(0.01f, 0.2f), "%i x (%i x %i), %i Images. Ignoring %i", Pages, m_AtlasW,m_AtlasH, m_Added.GetElementC(), m_ToPassThrough.size());

}

void		DX8GenAtlas::AddSpriteFile(const std::string& Name)
{
	LuaState* pState = script::ScriptHelper::CreateLuaState();
	if (!ScriptHelper::DoFile(*pState, Name))
	{
		_LOG(MSG_ERROR, "Unable to load Script from " << Name << "!");
		LuaState::Destroy( pState );
	}  else
	{
		{
			LuaObject	ObjTable = pState->GetGlobal("Sprites");
			if (ObjTable.IsTable())
			{
				for (int i=0; i<ObjTable.GetTableCount(); i++)
				{
					LuaObject	Obj = ObjTable.GetByIndex(i+1);
					std::string Sprite = ScriptHelper::GetTableString(Obj, "Name"); 
					bool		IgnoreAtlas = ScriptHelper::GetTableBool(Obj, "IgnoreAtlas", false); 
				
					if (!IgnoreAtlas)
					{
						AddToProcess(Sprite);
					}
				}
			}
		}
		LuaState::Destroy( pState );
	}
	std::sort(m_ToProcess.begin(), m_ToProcess.end(),  AtlasEntry::Sort);
}

void		DX8GenAtlas::AddToProcess(const std::string& SpriteName)
{
	AtlasEntry*	pEntry = new AtlasEntry;
	pEntry->Sprite.Create(SpriteName);
	Image* pImg = pEntry->Sprite.GetImage();
	ui::SpriteInstance*	pInst = pEntry->Sprite.GetSpriteInst();

	if (!pInst || !pImg)
	{
		delete pEntry;
		return;
	}
	pEntry->Rect.w = pImg->GetWidth() -(int)pInst->ClipX;
	pEntry->Rect.h = pImg->GetHeight() -(int)pInst->ClipY;
	pEntry->Rect.x = pEntry->Rect.y = -1;	// not set yet
	pEntry->Name = pImg->GetFile();

/*	if (pEntry->Rect.w >= m_AtlasW && pEntry->Rect.h >= m_AtlasH)
	{
		//	just pass this one through.. it'll basically fill or exceed a page all of it's own
		// so we'll just export it directly
		m_ToPassThrough.push_back(pEntry);
	} else*/
	{
		m_ToProcess.push_back(pEntry);
	}
}

DX8GenAtlas::AtlasPage*	DX8GenAtlas::AddEntry(DX8GenAtlas::AtlasEntry* pEntry)
{
	for (unsigned int i=0; i<m_Pages.size(); i++)
	{
		AtlasPage* pPage = m_Pages[i];
		if (pPage->AddEntry(pEntry))
		{
			return pPage;	// success
		}
	}
	AtlasPage* pPage = new AtlasPage;
	pPage->Root.Rect.w = m_AtlasW;
	pPage->Root.Rect.h = m_AtlasH;
	HRESULT  hRes = GetDXGraphics()->GetDevice()->CreateTexture(  m_AtlasW, m_AtlasH, 0,
												0, 
												D3DFMT_A8R8G8B8,
												D3DPOOL_MANAGED, 
												&pPage->pTexture);
	assert(SUCCEEDED(hRes));
	D3DLOCKED_RECT d3dlr;
	pPage->pTexture->LockRect( 0, &d3dlr, 0, 0 );
	memset(d3dlr.pBits, ColorRGBA(0, 0, 0, 0), m_AtlasH * d3dlr.Pitch);
	pPage->pTexture->UnlockRect(0);


	pPage->AddEntry(pEntry);
	m_Pages.push_back(pPage);
	return pPage;
}

void		DX8GenAtlas::SetupAtlas(int MaxWidth, int MaxHeight, int Depth)
{
	m_AtlasW = MaxWidth;
	m_AtlasH = MaxHeight; 
	m_AtlasD = Depth;

}
void		DX8GenAtlas::GenerateAtlas(const std::string& BasePath, std::vector<std::string>& List)
{
	while (!m_ToProcess.empty())
	{
		ProcessSingle();
	}
	// save out
	SaveAtlas(BasePath, List);
}

void	DX8GenAtlas::OutputAtlasElement(DX8GenAtlas::AtlasEntry* pEntry, void* pData)
{
	WriteLua	*pWL = (WriteLua*)pData;
	pWL->StartTable();
		pWL->WritePair("PageIndex", pEntry->PageIndex);
		pWL->WritePair("Name", pEntry->Name);
		pWL->WritePair("x", pEntry->Rect.x);
		pWL->WritePair("y", pEntry->Rect.y);
		pWL->WritePair("w", pEntry->Rect.w);
		pWL->WritePair("h", pEntry->Rect.h);
	pWL->CloseTable();
}
void		DX8GenAtlas::SaveAtlas(const std::string& BasePath, std::vector<std::string>& List)
{
	std::string	Base = BasePath+"/atlas_";
	for (unsigned int i=0; i<m_Pages.size(); i++)
	{
		char	Buffer[256];
		sprintf(Buffer, "%s%02i.tga", Base.c_str(), i);
		m_Pages[i]->Save(Buffer);
		m_Pages[i]->Root.UpdateFile(i);	// let all our entries know as well

		List.push_back(Buffer);
	}

	// then output the override file

	WriteLua	WL;
	WL.Open(BasePath+"/atlas.lua");

	// write the header
	WL.StartTable("AtlasInfo");
	WL.WritePair("Base", Base);
	WL.WritePair("Pages", (int)m_Pages.size());
	WL.WritePair("Width", m_AtlasW);
	WL.WritePair("Height", m_AtlasH);
	WL.CloseTable(false);

	// write the entries
	WL.StartTable("Atlas");
	m_Added.ForEach(OutputAtlasElement, &WL);
	WL.CloseTable(false);
}


void		DX8GenAtlas::ProcessSingle()
{
	if (m_ToProcess.empty()) return;
	AtlasEntry*	pEntry = m_ToProcess[m_ToProcess.size()-1];
	m_ToProcess.resize(m_ToProcess.size()-1);
	// get filename
	
	if (AtlasEntry*	pExists = m_Added.GetElement(pEntry->Name))
	{
		// already added.. probably a different list, or something
		delete pEntry;
		return;
	}
	m_Added.AddElement(pEntry->Name, pEntry);
	AddEntry(pEntry);
}

bool		DX8GenAtlas::AtlasPage::AddEntry(DX8GenAtlas::AtlasEntry* pEntry)
{
	AtlasNode*	pNode = Root.Insert(pEntry);
	if (!pNode) return false;	// not enough space

	// source
	Dx8Surface* pFromSurface =  (Dx8Surface* )pEntry->Sprite.GetImage()->GetSurface();
	if (!pFromSurface) return false;
	IDirect3DSurface8 *	pSrcSurf = NULL;
	pFromSurface->GetTexture()->GetSurfaceLevel(0, &pSrcSurf);
	if (!pSrcSurf) return false;

	
	IDirect3DSurface8 *	pDestSurf = NULL;
	pTexture->GetSurfaceLevel(0, &pDestSurf);
	if (!pDestSurf) return false;

	RECT	src;
	src.left = 0;
	src.top = 0;
	src.right = pEntry->Rect.w;
	src.bottom = pEntry->Rect.h;

	POINT	dst;
	dst.x = pEntry->Rect.x;
	dst.y = pEntry->Rect.y;

	GetDXGraphics()->GetDevice()->CopyRects(pSrcSurf, &src, 1, pDestSurf, &dst);

	return true;
}

DX8GenAtlas::AtlasNode*		DX8GenAtlas::AtlasNode::GetNodeByPos(const math::Vec2& P, const math::Vec2& TL, const math::Vec2& Size)
{
	float	x = TL.x + (Size.x*Rect.x / 1024.0f);
	float	y = TL.y + (Size.y*Rect.y / 1024.0f);
	float	w = (Size.x * (Rect.w / 1024.0f));
	float	h = (Size.y * (Rect.h / 1024.0f));

	if (UTIL_RANGE(P.x, x, x+w) && UTIL_RANGE(P.y, y, y+h))
	{
		if (pEntry) return this;
		if (NodeA[0])
		{
			AtlasNode*	pNode = NodeA[0]->GetNodeByPos(P, TL, Size);
			if (pNode) return pNode;
		}
		if (NodeA[1])
		{
			return NodeA[1]->GetNodeByPos(P, TL, Size);
		}
	}
	return NULL;
}



void			DX8GenAtlas::AtlasNode::Render(const math::Vec2& TL, const math::Vec2& Size)
{
	dd::Manager& DD = dd::Manager::Instance();
	float	x = TL.x + (Size.x*Rect.x / 1024.0f);
	float	y = TL.y + (Size.y*Rect.y / 1024.0f);
	float	w = (Size.x * (Rect.w / 1024.0f));
	float	h = (Size.y * (Rect.h / 1024.0f));
	DD.AddBox2D(math::Vec2(x, y), math::Vec2(x+w, y+h));
	for (int i=0; i<2; i++)
	{
		if (NodeA[i])
		{
			NodeA[i]->Render(TL, Size);
		}
	}
}

DX8GenAtlas::AtlasNode*	DX8GenAtlas::AtlasNode::Insert(DX8GenAtlas::AtlasEntry* pToAdd)
{
	// if we're not a leaf (i.e. we have nodes on A and B side)
 	if (NodeA[0] && NodeA[1])
	{
		AtlasNode*	pSuccess = NodeA[0]->Insert(pToAdd);
		if (pSuccess) return pSuccess;	// found and added
		return NodeA[1]->Insert(pToAdd);	// try addin the other side. If it fails, it fails..
	} else
	{
		// already full?
		if (pEntry) return NULL;

		// if our rect is too small, return;
		if (!Rect.CanFit(pToAdd->Rect)) return NULL;

		// is it a perfect fit?
		if (Rect.PerfectFit(pToAdd->Rect)) 
		{
			pEntry = pToAdd;
			pToAdd->Rect.x = Rect.x;
			pToAdd->Rect.y = Rect.y;
			return this;
		}

		// sadly not a perfect fit.. split the tree and try again
		NodeA[0] = new AtlasNode();
		NodeA[1] = new AtlasNode();

		int		dw = Rect.w - pToAdd->Rect.w;
		int		dh = Rect.h - pToAdd->Rect.h;

		// split the space along the principal axis
		if (dw > dh)	// split along X
		{
			NodeA[0]->Rect = AtlasRect(Rect.x, Rect.y, pToAdd->Rect.w, Rect.h);	// principal
			NodeA[1]->Rect = AtlasRect(Rect.x+pToAdd->Rect.w, Rect.y, Rect.w-pToAdd->Rect.w, Rect.h);	// remains
		} else			// split along y
		{
			NodeA[0]->Rect = AtlasRect(Rect.x, Rect.y, Rect.w, pToAdd->Rect.h);	// principal
			NodeA[1]->Rect = AtlasRect(Rect.x, Rect.y+pToAdd->Rect.h, Rect.w, Rect.h-pToAdd->Rect.h);	// remain
		}
		return NodeA[0]->Insert(pToAdd);
	}
}

void	DX8GenAtlas::AtlasNode::UpdateFile(int PageIndex)
{
	if (pEntry)
		pEntry->PageIndex = PageIndex;
	if (NodeA[0])
		NodeA[0]->UpdateFile(PageIndex);
	if (NodeA[1])
		NodeA[1]->UpdateFile(PageIndex);
}

#pragma pack(push, 1)
struct TGA_HEADER
{
    byte  identsize;          // size of ID field that follows 18 byte header (0 usually)
    byte  colourmaptype;      // type of colour map 0=none, 1=has palette
    byte  imagetype;          // type of image 0=none,1=indexed,2=rgb,3=grey,+8=rle packed

    short colourmapstart;     // first colour map entry in palette
    short colourmaplength;    // number of colours in palette
    byte  colourmapbits;      // number of bits per palette entry 15,16,24,32

    short xstart;             // image x origin
    short ystart;             // image y origin
    short width;              // image width in pixels
    short height;             // image height in pixels
    byte  bits;               // image bits per pixel 8,16,24,32
    byte  descriptor;         // image descriptor bits (vh flip bits)
    // pixel data follows header
};
#pragma pack(pop)

void	DX8GenAtlas::AtlasPage::Save(const std::string& Name)
{
	TGA_HEADER	Header;
	memset(&Header, 0, sizeof(Header));

	Header.width = Root.Rect.w;
	Header.height = Root.Rect.h;
	Header.bits  = 32;
	Header.descriptor = 0x08;		//32bit RGBA, with origin in top-left
	Header.imagetype = 2;

	
	FILE*	pFile = fopen(Name.c_str(), "wb");
	if (!pFile) return;
	fwrite(&Header, 1, sizeof(Header), pFile);
	
	// lock the rect and copy all
	D3DLOCKED_RECT	Rect;
	IDirect3DSurface8 *	pSurf = 0;
	pTexture->GetSurfaceLevel(0, &pSurf);
	if (!pSurf) 
	{
		fclose(pFile);
		return;
	};
	HRESULT hres = pSurf->LockRect(&Rect, NULL, D3DLOCK_READONLY);
	if (FAILED(hres))
	{
		fclose(pFile);
		pSurf->Release();
		return;
	}

	for (int y=Root.Rect.h-1; y>=0; y--)
	{
		DWORD*	pSource = (DWORD*)((unsigned char*)(Rect.pBits) + (Rect.Pitch*y));
		for (int x=0; x<(int)Root.Rect.w; x++)
		{
			DWORD	SourceColor = pSource[x];
			fwrite(&SourceColor, 1, sizeof(SourceColor), pFile);
		}
	}

	hres = pSurf->UnlockRect();
	pSurf->Release();
	fclose(pFile);

}
